﻿using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using NColony.Dependencies.Contracts;

namespace NColony.Dependencies
{
    /// <summary>
    /// Static implementation of inversion-of-control containers.
    /// </summary>
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class DefaultDependencyManager : IDependencyManager, IDisposable
    {
        private readonly CompositionContainer container;

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultDependencyManager"/> class.
        /// </summary>
        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Cannot dispose of assemblyCatalog until the container is disposed of.")]
        public DefaultDependencyManager()
        {
            var executingAssembly = Assembly.GetExecutingAssembly();
            var assemblyCatalog = new AssemblyCatalog(executingAssembly);

            this.container = new CompositionContainer(assemblyCatalog);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultDependencyManager"/> class.
        /// </summary>
        /// <param name="assemblies">The assemblies.</param>
        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Cannot dispose of assemblyCatalog until the container is disposed of.")]
        public DefaultDependencyManager(params Assembly[] assemblies)
        {
            var assemblyCatalogs = assemblies.Select(a => new AssemblyCatalog(a));
            var aggregateCatalog = new AggregateCatalog(assemblyCatalogs);
            
            this.container = new CompositionContainer(aggregateCatalog);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultDependencyManager"/> class.
        /// </summary>
        /// <param name="types">The types.</param>
        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Cannot dispose of assemblyCatalog until the container is disposed of.")]
        public DefaultDependencyManager(params Type[] types)
        {
            var typeCatalog = new TypeCatalog(types);
            
            this.container = new CompositionContainer(typeCatalog);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultDependencyManager"/> class.
        /// </summary>
        /// <param name="container">The container.</param>
        public DefaultDependencyManager(CompositionContainer container)
        {
            this.container = container;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultDependencyManager"/> class.
        /// </summary>
        /// <param name="catalogs">The catalogs.</param>
        [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Cannot dispose of assemblyCatalog until the container is disposed of.")]
        public DefaultDependencyManager(params ComposablePartCatalog[] catalogs)
        {
            var aggregateCatalog = new AggregateCatalog(catalogs);
            
            this.container = new CompositionContainer(aggregateCatalog);
        }

        /// <summary>
        /// Resolves the dependencies.
        /// </summary>
        /// <param name="objectsWithDependencies">The objects with dependencies.</param>
        public void ResolveDependencies(params object[] objectsWithDependencies)
        {
            this.container.ComposeParts(objectsWithDependencies);
        }

        /// <summary>
        /// Creates a lazy instance.
        /// </summary>
        /// <typeparam name="T">The type of instance to create.</typeparam>
        /// <returns>A lazy instance.</returns>
        public Lazy<T> CreateLazyInstance<T>()
        {
            return this.container.GetExport<T>();
        }

        /// <summary>
        /// Creates a lazy instance.
        /// </summary>
        /// <typeparam name="T">The type of instance to create.</typeparam>
        /// <param name="dependencyLookupKey">The dependency lookup key.</param>
        /// <returns>A lazy instance.</returns>
        public Lazy<T> CreateLazyInstance<T>(string dependencyLookupKey)
        {
            return this.container.GetExport<T>(dependencyLookupKey);
        }

        /// <summary>
        /// Creates the instance.
        /// </summary>
        /// <typeparam name="T">The type of instance to create.</typeparam>
        /// <returns>An instance if found, otherwise throws an Exception.</returns>
        public T CreateInstance<T>()
        {
            return this.container.GetExportedValue<T>();
        }

        /// <summary>
        /// Creates the instance.
        /// </summary>
        /// <typeparam name="T">The type of instance to create.</typeparam>
        /// <param name="dependencyLookupKey">The dependency lookup key.</param>
        /// <returns>
        /// An instance if found, otherwise throws an Exception.
        /// </returns>
        public T CreateInstance<T>(string dependencyLookupKey)
        {
            return this.container.GetExportedValue<T>(dependencyLookupKey);
        }

        /// <summary>
        /// Creates the instance or default.
        /// </summary>
        /// <typeparam name="T">The type of instance to create.</typeparam>
        /// <returns>An instance if found, otherwise null.</returns>
        public T CreateInstanceOrDefault<T>()
        {
            return this.container.GetExportedValueOrDefault<T>();
        }

        /// <summary>
        /// Creates the instance or default.
        /// </summary>
        /// <typeparam name="T">The type of instance to create.</typeparam>
        /// <param name="dependencyLookupKey">The dependency lookup key.</param>
        /// <returns>An instance if found, otherwise null.</returns>
        public T CreateInstanceOrDefault<T>(string dependencyLookupKey)
        {
            return this.container.GetExportedValueOrDefault<T>(dependencyLookupKey);
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.container != null)
                {
                    this.container.Dispose();
                }
            }
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="DefaultDependencyManager"/> is reclaimed by garbage collection.
        /// </summary>
        ~DefaultDependencyManager()
        {
            this.Dispose(false);
        }
    }
}